const now = () => Date.now();

function debounce(func, wait = 0) {
	let lastArgs;
	let lastThis;
	let result;
	let timerId;
	let lastCallTime;
	let lastInvokeTime = 0;

	if (typeof func != 'function') {
		throw new TypeError('Expected a function');
	}

	function invokeFunc(time) {
		const args = lastArgs;
		const thisArg = lastThis;

		lastArgs = lastThis = undefined;
		lastInvokeTime = time;
		result = func.apply(thisArg, args);
		return result;
	}

	function leadingEdge(time) {
		lastInvokeTime = time;
		timerId = setTimeout(timerExpired, wait);
		return result;
	}

	function remainingWait(time) {
		const timeSinceLastCall = time - lastCallTime;
		// const timeSinceLastInvoke = time - lastInvokeTime;
		const result = wait - timeSinceLastCall;
		return result;
	}

	function shouldInvoke(time) {
		const timeSinceLastCall = time - lastCallTime;
		// const timeSinceLastInvoke = time - lastInvokeTime;
		return lastCallTime === undefined || timeSinceLastCall >= wait || timeSinceLastCall < 0;
	}

	function timerExpired() {
		const time = now();
		if (shouldInvoke(time)) {
			return trailingEdge(time);
		}
		// Restart the timer.
		timerId = setTimeout(timerExpired, remainingWait(time));
	}

	function trailingEdge(time) {
		timerId = undefined;
		if (lastArgs) {
			return invokeFunc(time);
		}
		lastArgs = lastThis = undefined;
		return result;
	}

	function debounced(...args) {
		const time = now();
		const isInvoking = shouldInvoke(time);
		lastArgs = args;
		lastThis = this;
		lastCallTime = time;

		if (isInvoking) {
			if (timerId === undefined) {
				return leadingEdge(lastCallTime);
			}
		}
		if (timerId === undefined) {
			timerId = setTimeout(timerExpired, wait);
		}
		return result;
	}

	return debounced;
}

export { debounce };
